home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1999 April / macformat-075.iso / Shareware Plus / Applications / Alpha / Tcl / Modes / diffMode.tcl < prev    next >
Encoding:
Text File  |  1999-01-31  |  30.5 KB  |  1,072 lines  |  [TEXT/ALFA]

  1. ## -*-Tcl-*-
  2.  # ###################################################################
  3.  #  Vince's Additions - an extension package for Alpha
  4.  # 
  5.  #  FILE: "diffMode.tcl"
  6.  #                                    created: 7/3/95 {11:15:02 pm} 
  7.  #                                last update: 31/1/1999 {12:22:14 am} 
  8.  #  Author: Vince Darley
  9.  #  E-mail: <darley@fas.harvard.edu>
  10.  #    mail: Division of Engineering and Applied Sciences, Harvard University
  11.  #          Oxford Street, Cambridge MA 02138, USA
  12.  #     www: <http://www.fas.harvard.edu/~darley/>
  13.  #  
  14.  # improvements Copyright (c) 1997-1998  Vince Darley, all rights reserved
  15.  # 
  16.  #  Description: 
  17.  #  
  18.  #  Largely re-written Diff mode for Alpha.  Still under construction,
  19.  #  but already a lot better than the old one.  Basic features:
  20.  #  
  21.  #  A 'Diff' menu, which contains commonly used options.
  22.  #  
  23.  #  Uses Alpha's 'marks' so that you can patch diffs back and forth
  24.  #  between files without losing the correct location in the file.
  25.  #  (previously if you modified one of the original windows, all line
  26.  #  numbers after that would be incorrect)
  27.  #  
  28.  #  Limitations:
  29.  #  
  30.  #  Sadly a lot of Alpha's window manipulation commands only work
  31.  #  on the foremost window.  This means this code is slowed down a
  32.  #  lot because it often has to bring a window to the front before
  33.  #  reading/writing into it.  There is a flag to setup a hack which
  34.  #  helps with this, at the expense of colours in the windows.
  35.  # 
  36.  #  History:
  37.  # 
  38.  #  modified by  rev reason
  39.  #  -------- --- --- -----------
  40.  #  7/3/95   Pete? 1.0 original
  41.  #  3/9/97   VMD 2.0 much improved version
  42.  #  03/23/98 VMD and Jon Guyer 2.0-3.0 various fixes and Voodoo 
  43.  # ###################################################################
  44.  ##
  45.  
  46. # Usage: diff [-#] [-abBcdefhHilnNprstTuvw] [-C lines] [-F regexp] [-I regexp]
  47. #        [-L label [-L label]] [-S file] [-D symbol] [+ignore-blank-lines]
  48. #        [+context[=lines]] [+unified[=lines]] [+ifdef=symbol]
  49. #        [+show-function-line=regexp]
  50. #        [+speed-large-files] [+ignore-matching-lines=regexp] [+new-file]
  51. #        [+initial-tab] [+starting-file=file] [+text] [+all-text] [+ascii]
  52. #        [+minimal] [+ignore-space-change] [+ed] [+reversed-ed] [+ignore-case]
  53. #        [+print] [+rcs] [+show-c-function] [+binary] [+brief] [+recursive]
  54. #        [+report-identical-files] [+expand-tabs] [+ignore-all-space]
  55. #        [+file-label=label [+file-label=label]] [+version] path1 path2
  56.  
  57. alpha::mode Diff 3.0b8 diffMenu {*.diff *.patch} {diffMenu} {
  58.     alpha::package require -loose AlphaTcl 7.1.6
  59.     addMenu diffMenu •288
  60.     namespace eval compare {}
  61.     menu::insert Utils submenu 0 compare
  62.     menu::insert compare items end "windows" "files…" "directories…"
  63.     hook::register requireOpenWindowsHook [list compare windows] 2
  64.     ensureset DiffSig DIFF
  65.     set Diff::handlers(Diff-mode) Diff::runInsideAlpha
  66.     ensureset Diff::handler Diff-mode
  67.     # handler of Diff 
  68.     newPref var Diff::handler "Diff-mode" global "" Diff::handlers array
  69. } uninstall {
  70.     file delete "$pkg_file"
  71.     file delete [file join ${HOME} Tools "GNU Diff"]
  72. } maintainer {
  73.     "Vince Darley" darley@fas.harvard.edu <http://www.fas.harvard.edu/~darley/>
  74. } help {file "Diff Help"}
  75.  
  76. array set DiffAppSignatures {
  77.     GnuDiff DIFF
  78. }
  79. array set DiffAppScripts {
  80.     GnuDiff {
  81.     {dosc -c $quotedSig -s $flags}
  82.     }
  83. }
  84.  
  85. proc diffMenu {} {}
  86.  
  87. # Generally best to use this setting, but some actions can be a bit
  88. # slower with it on.  Allows you to patch changes back and forth
  89. # between windows automatically, which is otherwise not possible
  90. newPref f useSophisticatedDiffMarking 1 Diff
  91. # A good idea, but can mess up window colours sometimes
  92. # (it's a bit of a hack)
  93. newPref f useFastWindowSwapping 1 Diff
  94. # Slows things down in that it has to scan through Alpha's list of marks
  95. # to find the correct positions for each window, but speeds things up
  96. # because it doesn't need to activate each window in turn.  Try it and see
  97. # for yourself.
  98. newPref f useMarksDontBringToFront 1 Diff
  99. # Up/Down arrows both scroll the diff window and synchronise the viewed
  100. # portion of text in the document windows
  101. newPref f synchroniseMoveAndView 1 Diff Diff::bindUpDown
  102. # You'll probably want this; may slow things down a bit though
  103. newPref f workaroundAlphaColourBug 1 Diff
  104. # Default lines of context to generate when asking Diff to do its magic
  105. newPref var linesOfContext 3 Diff
  106. # Other diff flags you want to send (ignore whitespace etc)
  107. newPref var diffFlags { } Diff
  108. # If you've imported a diff file from a Unix system, this option allows
  109. # you to use it with Alpha too.
  110. newPref f convertSlashToColonInPaths 1 Diff
  111. # If you've imported a diff file from a different directory structure,
  112. # you may need to remove a given prefix so Alpha can find your files
  113. # correctly.
  114. newPref v removeFilePrefix "" Diff
  115.  
  116. Menu -n $diffMenu -p Diff::menuProc -M Diff {
  117.     "rerunDiff"
  118.     "(-"
  119.     "/<I<BpatchIntoLeftWindow"
  120.     "/<I<BpatchIntoRightWindow"
  121.     "(-"
  122.     "cleanUpAndCloseWindows"
  123.     "(-"
  124.     "locateLeftWindow"
  125.     "locateRightWindow"
  126.     "locateLeftDir"
  127.     "locateRightDir"
  128.     "parseDiffWin"
  129. }
  130. Bind 0x7b <z> Diff::patchIntoLeftWindow Diff
  131. Bind 0x7c <z> Diff::patchIntoRightWindow Diff
  132.  
  133. if {[info tclversion] < 8.0} {
  134.     # Bind manually due to bug
  135.     Bind 0x7b <oz> Diff::patchIntoLeftWindow Diff
  136.     Bind 0x7c <oz> Diff::patchIntoRightWindow Diff
  137. }
  138. # do the rest
  139. Bind '\r'        Diff::Select    Diff
  140. Bind '\t'        Diff::View    Diff
  141. Bind Kpad. <c>        Diff::Win
  142. Bind Enter        {Diff::Down;Diff::Select}    Diff
  143. Bind Kpad0        {Diff::Up;Diff::Select}    Diff
  144.  
  145. hook::register closeHook Diff::closing Diff
  146. hook::register openHook Diff::opening Diff
  147.  
  148. proc Diff::bindUpDown {} {
  149.     global DiffmodeVars
  150.     if {$DiffmodeVars(synchroniseMoveAndView)} {
  151.     catch {unBind down         Diff::Down Diff}
  152.     catch {unBind up         Diff::Up Diff}
  153.     Bind down        {Diff::Down;Diff::View}    Diff
  154.     Bind up        {Diff::Up;Diff::View}    Diff
  155.     } else {
  156.     catch {unBind down        {Diff::Down;Diff::View}    Diff}
  157.     catch {unBind up        {Diff::Up;Diff::View}    Diff}
  158.     Bind down         Diff::Down Diff
  159.     Bind up         Diff::Up Diff
  160.     }
  161. }
  162.  
  163. Diff::bindUpDown
  164.  
  165. proc Diff::menuProc {menu item} {
  166.     Diff::$item
  167. }
  168.  
  169. proc Diff::locateLeftWindow {} {
  170.     global Diff::1
  171.     set Diff::1 [getfile "Select your left (old) file:"]
  172.     Diff::Display Diff::1 1 0 1
  173.     Diff::setMarksUp
  174.     if {[info exists Diff::1]} {Diff::mark ${Diff::1} 1 ""}
  175.     Diff::diffWinFront
  176. }
  177.  
  178. proc Diff::locateRightWindow {} {
  179.     global Diff::2
  180.     set Diff::2 [getfile "Select your right (new) file:"]
  181.     Diff::Display Diff::2 0 0 1
  182.     Diff::setMarksUp
  183.     if {[info exists Diff::2]} {Diff::mark ${Diff::2} 0 ""}
  184.     Diff::diffWinFront
  185. }
  186.  
  187. proc Diff::locateLeftDir {} {
  188.     global Diff::leftDir
  189.     set Diff::leftDir [get_directory "Select your left (old) directory:"]
  190.     append Diff::leftDir :
  191. }
  192. proc Diff::locateRightDir {} {
  193.     global Diff::rightDir
  194.     set Diff::rightDir [get_directory "Select your right (new) directory:"]
  195.     append Diff::rightDir :
  196. }
  197.  
  198. proc Diff::rerunDiff {} {
  199.     global diffDir
  200.     Diff::diffWinFront
  201.     killWindow
  202.     if {$diffDir} {
  203.     Diff::execute 1 {* Directory Comparison *}
  204.     } else {
  205.     Diff::files
  206.     }
  207. }
  208.  
  209. proc Diff::cleanUpAndCloseWindows {} {
  210.     global Diff::1 Diff::2 diffDir
  211.     if {![catch {bringToFront ${Diff::1}}]} {
  212.     removeAllMarks diff-*
  213.     shrinkFull
  214.     killWindow
  215.     }
  216.     
  217.     if {![catch {bringToFront ${Diff::2}}]} {
  218.     removeAllMarks diff-*
  219.     shrinkFull
  220.     killWindow
  221.     }
  222.     Diff::diffWinFront
  223.     killWindow
  224. }
  225.  
  226. proc Diff::closing {{name ""}} {
  227.     global Diff::array Diff::Marked Diff::1 Diff::2
  228.     foreach var [info globals Diff::array*] {
  229.     global $var
  230.     if {[array exists $var]} { unset $var }
  231.     }
  232.     catch {unset Diff::Marked}
  233.     catch {unset Diff::1}
  234.     catch {unset Diff::2}
  235. }
  236.  
  237. ## 
  238.  # -------------------------------------------------------------------------
  239.  # 
  240.  # "Diff::opening" --
  241.  # 
  242.  #  This procedure is called whenever we open a diff window, whether 
  243.  #  a '.diff' file, or whether a window produced by this mode using
  244.  #  'Diff::execute'.  We parse its contents.
  245.  # -------------------------------------------------------------------------
  246.  ##
  247. proc Diff::opening {name} {
  248.     global Diff::window DiffmodeVars Diff::leftDir Diff::rightDir
  249.     set Diff::window $name
  250.     set Diff::leftDir ""
  251.     set Diff::rightDir ""
  252.     
  253.     if {$DiffmodeVars(useSophisticatedDiffMarking)} {
  254.     Diff::parseDiffWin
  255.     }
  256. }
  257.  
  258. # ◊◊◊◊ Parsing diff information ◊◊◊◊ #
  259.  
  260. proc Diff::parseDiffWin {} {
  261.     Diff::diffWinFront
  262.     global diffDir Diff::window
  263.     
  264.     set pos [minPos]
  265.     while 1 {
  266.     set res [search -s -n -f 1 -r 1 "^(diff.*|\[^- \n\r\]+)(\r|\n|\$)" $pos]
  267.     if {$res != ""} {
  268.         set pos [pos::math [lindex $res 0] + 1]
  269.         # if we picked up a 'diff...' line in a context diff
  270.         if {[lookAt $pos] == "i" && [lookAt [nextLineStart [lindex $res 0]]] == "*"} {
  271.         continue
  272.         }
  273.         set t [getText [lindex $res 0] [pos::math [lindex $res 1] - 1]]
  274.         if {[regexp {^\*+$} $t]} {
  275.         set diffDir 1
  276.         # check if the file has changed
  277.         if {[string index [set tt [getText [prevLineStart $pos] $pos]] 0] != " " \
  278.           && [lookAt [pos::math $pos - 3]] != "-" } {
  279.             set to [lindex $tt 1]
  280.             regexp " (.*)\t" $tt "" to
  281.             set p [prevLineStart $pos]
  282.             regexp " (.*)\t" [getText [prevLineStart $p] $p] from
  283.             regsub -all "/" $from ":" from
  284.             regsub -all "/" $to ":" to
  285.             lappend got [list "diff" $from $to]
  286.         }
  287.         set from [lindex [eval getText [search -s -n -f 1 -r 1 {^\*\*\* [0-9]+,[0-9]+} $pos]] 1]
  288.         set to [lindex [eval getText [search -s -n -f 1 -r 1 {^--- [0-9]+,[0-9]+} $pos]] 1]
  289.         lappend got "$from $to"
  290.         } else {
  291.         lappend got $t
  292.         }
  293.     } else {
  294.         break
  295.     }
  296.     }
  297.     set Diff::window [win::Current]
  298.     # now stored all diff items in the list 'got'
  299.     if {[info exists got]} {Diff::storeMarks $got}
  300.     Diff::diffWinFront
  301.     global tileTop tileWidth tileHeight tileLeft
  302.     set top [expr {$tileTop + $tileHeight - 178}]
  303.     sizeWin ${Diff::window} [expr {$tileWidth - 6}] 178
  304.     moveWin ${Diff::window} $tileLeft $top
  305.     
  306. }
  307.  
  308. proc Diff::storeMarks {diffs} {
  309.     global Diff::1 Diff::2 Diff::array
  310.     set suff ""
  311.     foreach m $diffs {
  312.     if {[regexp {^diff} $m]} {
  313.         set suff "/[file tail [lindex $m end]]"
  314.         global Diff::array${suff}
  315.         continue
  316.     }
  317.     set Diff::array${suff}($m) ""
  318.     }
  319. }
  320.  
  321. proc Diff::setMarksUp {{suff ""}} {
  322.     global Diff::array${suff}
  323.     foreach m [array names Diff::array$suff] {
  324.     set scanned [Diff::parseDiffString $m]
  325.     if {[scan $scanned "%s %d %d %d %d" \
  326.       char start1 end1 start2 end2] != 5} { error "Bad diff list!" }
  327.     if {$scanned != ""} {
  328.         set Diff::array${suff}($m) $scanned
  329.     }
  330.     }
  331. }
  332.  
  333. proc Diff::mark {win left {suff ""}} {
  334.     global Diff::array$suff DiffmodeVars
  335.     if {$win != ""} {
  336.     # Alpha somehow remembers the last mode in which it adjusts
  337.     # the window and so forgets all the colours if we cheat the
  338.     # mode switch.
  339.     if {$DiffmodeVars(workaroundAlphaColourBug)} {
  340.         bringToFront $win
  341.     } else {
  342.         Diff::BringToFront $win
  343.     }
  344.     # not strictly necessary, but cleaner
  345.     removeAllMarks diff-*
  346.     if {$left} {
  347.         foreach m [array names Diff::array$suff] {
  348.         scan [set Diff::array${suff}($m)] "%s %d %d" char start1 end1
  349.         setNamedMark "diff-$m" $start1 $start1 $end1
  350.         }
  351.     } else {
  352.         foreach m [array names Diff::array$suff] {
  353.         scan [set Diff::array${suff}($m)] "%s %d %d %d %d" char start1 end1 start2 end2
  354.         setNamedMark "diff-$m" $start2 $start2 $end2
  355.         }
  356.     }
  357.     }
  358. }
  359.  
  360. proc Diff::markUpWindow {diffs} {
  361.     alertnote "Currently a little obsolete; shouldn't be called!"
  362.     if {[info exists Diff::1]} {
  363.     Diff::BringToFront ${Diff::1}
  364.     # not strictly necessry, but cleaner
  365.     removeAllMarks diff-*
  366.     foreach m $diffs {
  367.         scan [set Diff::array($m)] "%s %d %d" char start1 end1
  368.         setNamedMark "diff-$m" $start1 $start1 $end1
  369.     }
  370.     }
  371.     if {[info exists Diff::2]} {
  372.     Diff::BringToFront ${Diff::2}
  373.     # not strictly necessry, but cleaner
  374.     removeAllMarks diff-*
  375.     foreach m $diffs {
  376.         scan [set Diff::array($m)] "%s %d %d %d %d" char start1 end1 start2 end2
  377.         setNamedMark "diff-$m" $start2 $start2 $end2
  378.     }
  379.     }
  380.     
  381. }
  382.  
  383. proc Diff::parseDiffString {text} {
  384.     global Diff::1 Diff::2
  385.     if {![regexp {[acd]} $text char]} {
  386.     # context sensitive
  387.     set char "c"
  388.     if {[scan $text "%d,%d %d,%d" one oned two twod] != 4} {
  389.         return
  390.     }
  391.     } else {
  392.     set res [split $text $char]
  393.     if {![scan [lindex $res 0] "%d,%d" one oned]} return
  394.     if {![scan [lindex $res 1] "%d,%d" two twod]} return
  395.     if {![info exists oned]} { set oned $one }
  396.     if {![info exists twod]} { set twod $two }
  397.     }
  398.     
  399.     if {[info exists Diff::1]} {
  400.     set res [list $char [rowColToPos -w ${Diff::1} $one 0]]
  401.     if {$char != "a"} {
  402.         lappend res [rowColToPos -w ${Diff::1} [expr {$oned + 1}] 0]
  403.     } else {
  404.         lappend res [rowColToPos -w ${Diff::1} $oned 1]
  405.     }
  406.     } else {
  407.     set res [list $char -1 -1]
  408.     }
  409.     
  410.     if {[info exists Diff::2]} {
  411.     lappend res [rowColToPos -w ${Diff::2} $two 0]
  412.     if {$char != "d"} {
  413.         lappend res [rowColToPos -w ${Diff::2} [expr {$twod + 1}] 0]]
  414.     } else {
  415.         lappend res [rowColToPos -w ${Diff::2} $twod 1]
  416.     }
  417.     } else {
  418.     lappend res -1 -1
  419.     }
  420.     return $res
  421. }
  422.  
  423. proc Diff::parseDiffLine {text {is_pos 0}} {
  424.     if {$is_pos} {
  425.     set text [Diff::line $text]
  426.     }
  427.     return [Diff::parseDiffString $text]
  428. }
  429.  
  430. proc Diff::line {pos {f ""}} {
  431.     global diffDir Diff::window DiffmodeVars
  432.     if {$diffDir} {
  433.     if {$f != ""} {upvar $f files}
  434.     if {[lookAt $pos] == "*" || [catch {search -s -f 0 -r 1 "^diff.*(\r|\n|\$)" $pos} res]} {
  435.         set p $pos
  436.         while 1 {
  437.         set res [search -s -f 0 -r 1 "^\\*+(\r|\n|\$)" $p]
  438.         set p [pos::math [lindex $res 0] - 2]
  439.         if {[lookAt [lineStart $p]] != " " && [lookAt $p] != "-"} break
  440.         }
  441.         regexp " (.*)\t" [getText [lineStart $p] $p] "" to
  442.         regexp " (.*)\t" [getText [prevLineStart $p] [lineStart $p]] "" from
  443.         if {[set pr $DiffmodeVars(removeFilePrefix)] != ""} {
  444.         regsub -all "/\./" $to "/" to
  445.         if {[string first $pr $to] == 0} {
  446.             set to [string range $to [string length $pr] end]
  447.         }
  448.         regsub -all "/\./" $from "/" from
  449.         if {[string first $pr $from] == 0} {
  450.             set from [string range $from [string length $pr] end]
  451.         }
  452.         }
  453.         set files [list $from $to]
  454.         set tfrom [lindex [eval getText [search -s -n -f 1 -r 1 {^\*\*\* [0-9]+,[0-9]+} [getPos]]] 1]
  455.         set tto [lindex [eval getText [search -s -n -f 1 -r 1 {^--- [0-9]+,[0-9]+} [getPos]]] 1]
  456.         set text "$tfrom $tto"        
  457.     } else {
  458.         set llen [llength [set files [eval getText $res]]]
  459.         set files [lrange $files [expr {$llen -2}] end]
  460.         set text [getText [lineStart $pos] [pos::math [nextLineStart $pos] - 1]]
  461.     }
  462.     if {$DiffmodeVars(convertSlashToColonInPaths)} {
  463.         regsub -all "/" $files ":" files
  464.     }
  465.     set f [lindex $files end]
  466.     set suff "/[file tail $f]"
  467.     } else {
  468.     set suff ""
  469.     set text [getText [lineStart $pos] [pos::math [nextLineStart $pos] - 1]]
  470.     }
  471.     return "${text}${suff}"
  472. }
  473.  
  474. # ◊◊◊◊ Patching routines ◊◊◊◊ #
  475. proc Diff::patch {w1 w2 left} {
  476.     global DiffmodeVars
  477.     if {$DiffmodeVars(useSophisticatedDiffMarking)} {
  478.     Diff::patchSophisticated $w1 $w2 $left
  479.     } else {
  480.     Diff::patchOld $w1 $w2 $left
  481.     }
  482. }
  483. proc Diff::patchSophisticated {ww1 ww2 left} {
  484.     upvar \#0 $ww1 w1
  485.     upvar \#0 $ww2 w2
  486.     set code [Diff::line [getPos]]
  487.     regexp {([^/]+)(.*)} $code "" mark suff
  488.     global Diff::array${suff}
  489.     if {![info exists w1]} { alpha::errorAlert "No such window" }
  490.     switch "[lindex [set Diff::array${suff}($mark)] 0]${left}" {
  491.     "c1" -
  492.     "c0" {
  493.         if {[info exists w2]} { 
  494.         Diff::BringToFront ${w2}
  495.         gotoMark "diff-$mark"
  496.         set text [getSelect]
  497.         } else {
  498.         # we assume the line is selected in the diff-win
  499.         if {$left} {
  500.             set p [selEnd]
  501.             set e [search -s -f 1 -r 1 {^---.*$} $p]
  502.             set p [lindex $e 1]
  503.             set e [search -s -f 1 -r 1 {^[^>]} $p]
  504.             set text [getText $p [lindex $e 0]]
  505.             regsub -all "\[\n\r\]> " $text "\r" text
  506.             set text [string range $text 1 end]
  507.         } else {
  508.             set p [selEnd]
  509.             set e [search -s -f 1 -r 1 {^---} $p]
  510.             set text [getText $p [lindex $e 0]]
  511.             regsub -all "\[\r\n\]< " $text "\r" text
  512.             set text [string range $text 1 end]
  513.         }
  514.         }
  515.         Diff::BringToFront ${w1}
  516.         gotoMark "diff-$mark"
  517.         replaceText [getPos] [selEnd] $text
  518.     }
  519.     "d1" -
  520.     "a0" {
  521.         Diff::BringToFront ${w1}
  522.         gotoMark "diff-$mark"
  523.         deleteText [getPos] [selEnd]
  524.     }
  525.     "a1" -
  526.     "d0" {
  527.         if {[info exists w2]} { 
  528.         Diff::BringToFront ${w2}
  529.         gotoMark "diff-$mark"
  530.         set text [getSelect]
  531.         } else {
  532.         # we assume the line is selected in the diff-win
  533.         if {$left} {
  534.             set p [selEnd]
  535.             set e [search -s -f 1 -r 1 {^---.*$} $p]
  536.             set p [lindex $e 1]
  537.             set e [search -s -f 1 -r 1 {^[^>]} $p]
  538.             set text [getText $p [lindex $e 0]]
  539.             regsub -all "\[\n\r\]> " $text "\r" text
  540.             set text [string range $text 1 end]
  541.         } else {
  542.             set p [selEnd]
  543.             set e [search -s -f 1 -r 1 {^---} $p]
  544.             set text [getText $p [lindex $e 0]]
  545.             regsub -all "\[\n\r\]< " $text "\r" text
  546.             set text [string range $text 1 end]
  547.         }
  548.         }
  549.         Diff::BringToFront ${w1}
  550.         gotoMark "diff-$mark"
  551.         nextLine
  552.         insertText -w ${w1} $text
  553.     }
  554.     }
  555.     Diff::diffWinFront
  556. }
  557. proc Diff::patchOld {ww1 ww2 left} {
  558.     upvar \#0 $ww1 w1
  559.     upvar \#0 $ww2 w2
  560.     set code [Diff::line [getPos]]
  561.     if {[scan [Diff::parseDiffLine $code] "%s %d %d %d %d" \
  562.       char start1 end1 start2 end2] != 5} { return }
  563.     
  564.     switch $char${left} {
  565.     "c1" {
  566.         set text [getText -w ${w2} $start2 $end2]
  567.         bringToFront ${w1}
  568.         replaceText $start1 $end1 $text
  569.     }
  570.     "d1" {
  571.         bringToFront ${w1}
  572.         deleteText $start1 $end1
  573.     }
  574.     "a1" {
  575.         set text [getText -w ${w2} $start2 $end2]
  576.         set p [nextLineStart $start1]
  577.         # for some reason this single line won't work instead of the
  578.         # next two!
  579.         #select -w ${Diff::1} $p $p
  580.         bringToFront ${w1}
  581.         goto $p
  582.         insertText -w ${w1} $text
  583.     }
  584.     "c0" {
  585.         set text [getText -w ${w2} $start1 $end1]
  586.         bringToFront ${w1}
  587.         replaceText $start2 $end2 $text
  588.     }
  589.     "d0" {
  590.         set text [getText -w ${w2} $start1 $end1]
  591.         bringToFront ${w1}
  592.         goto $start2
  593.         nextLine
  594.         insertText $text
  595.     }
  596.     "a0" {
  597.         bringToFront ${w1}
  598.         deleteText $start2 $end2
  599.     }
  600.     }
  601.     message "Subsequent insertions will be screwed up"
  602. }
  603.  
  604. # In the diff-window, 'c' = cut from left, replace with given lines,
  605. # 'd' = delete from left, 'a' = add to left.
  606. proc Diff::patchIntoLeftWindow {} {
  607.     Diff::patch Diff::1 Diff::2 1
  608. }
  609.  
  610. proc Diff::patchIntoRightWindow {} {
  611.     Diff::patch Diff::2 Diff::1 0
  612. }
  613.  
  614. # ◊◊◊◊ Main comparison routines ◊◊◊◊ #
  615.  
  616. proc Diff::files {} {
  617.     global Diff::1 Diff::2
  618.     foreach f [list ${Diff::1} ${Diff::2}] {
  619.     if {[lsearch [winNames -f] $f] >= 0} {
  620.         getWinInfo -w $f arr
  621.         if {$arr(dirty)} {
  622.         bringToFront $f
  623.         if {![dialog::yesno "Save this window?"]} { error "Cancel"}
  624.         save
  625.         }
  626.     }
  627.     }
  628.     # make sure newer file is on the right
  629.     if {[file::secondIsOlder ${Diff::1} ${Diff::2}]} {
  630.     set d ${Diff::2}
  631.     set Diff::2 ${Diff::1}
  632.     set Diff::1 $d
  633.     unset d
  634.     }
  635.     Diff::run
  636. }
  637.  
  638. proc Diff::run {} {
  639.     global Diff::handler Diff::handlers
  640.     # call the registered procedure
  641.     [set Diff::handlers([set Diff::handler])]
  642. }
  643.  
  644. proc Diff::runInsideAlpha {} {
  645.     global Diff::1 Diff::2
  646.     Diff::Display Diff::1 1 0 1
  647.     Diff::Display Diff::2 0 0 1
  648.  
  649.     Diff::execute
  650. }
  651.  
  652. proc compare::directories {} {
  653.     global Diff::1 Diff::2
  654.     
  655.     set Diff::1 [get_directory -p "Select 'old' dir 1:"]
  656.     set Diff::2 [get_directory -p "Select 'new' dir 2:"]
  657.     
  658.     Diff::execute 1 {* Directory Comparison *}
  659. }
  660.  
  661. proc compare::files {} {
  662.     global Diff::1 Diff::2
  663.     
  664.     set Diff::1 [getfile "Select your 'old' file:"]
  665.     set Diff::2 [getfile "Select your 'new' file:"]
  666.     
  667.     Diff::files
  668. }
  669.  
  670. proc compare::windows {} {
  671.     global tileHeight tileWidth tileTop tileLeft
  672.     global Diff::1 Diff::2
  673.     
  674.     set wins [winNames -f]
  675.     if {[llength $wins] < 2} { message "Need 2 windows"; return }
  676.     
  677.     set Diff::1 [lindex $wins 0]
  678.     set Diff::2 [lindex $wins 1]
  679.     Diff::files
  680. }
  681.  
  682.  
  683. ## 
  684.  # -------------------------------------------------------------------------
  685.  # 
  686.  # "Diff::execute" --
  687.  # 
  688.  #  Modification of the original to optionally return the diff 
  689.  #  result, rather than opening it in a window
  690.  # 
  691.  # Results:
  692.  # 
  693.  #  Returns 1 if the files are the same and 0 if they differ
  694.  #  
  695.  #  If storeResult is true, the result of the diff operation is stored 
  696.  #  in the global Diff::result, rather than being opened in a window
  697.  # 
  698.  # --Version--Author------------------Changes-------------------------------
  699.  #    1.0     <keleher@cs.umd.edu> original
  700.  #    1.1     <j-guyer@nwu.edu> optionally return diff result in a global
  701.  #    1.2     <j-guyer@nwu.edu> flags set if files were open before compare
  702.  # -------------------------------------------------------------------------
  703.  ##
  704. proc Diff::execute {{isdir 0} {name {* File Comparison *}} {storeResult 0}} {
  705.     global DiffmodeVars \
  706.       Diff::1 Diff::2 win::Modes HOME diffDir Diff::result \
  707.       Diff::1Open Diff::2Open \
  708.       Diff::leftDir Diff::rightDir DiffSig
  709.     
  710.     set Diff::leftDir ""
  711.     set Diff::rightDir ""
  712.     set diffDir $isdir
  713.     
  714.     message "Launching 'GNU Diff'"
  715.     set flags $DiffmodeVars(diffFlags)
  716.     if {$DiffmodeVars(linesOfContext) != 0} {
  717.     append flags " -C $DiffmodeVars(linesOfContext)"
  718.     }
  719.     message "Starting diff…"
  720.     append flags " \"[stripNameCount ${Diff::1}]\" \"[stripNameCount ${Diff::2}]\""
  721.     set dtext [app::runScript Diff "Diff application" "" 1 0 $flags]
  722.     message "Starting diff…done"
  723.     
  724.     if {[lsearch [winNames -f] ${Diff::1}] >= 0} {
  725.     set Diff::1Open 1
  726.     } else {
  727.     set Diff::1Open 0
  728.     }
  729.     if {[lsearch [winNames -f] ${Diff::2}] >= 0} {
  730.     set Diff::2Open 1
  731.     } else {
  732.     set Diff::2Open 0
  733.     }
  734.     
  735.     if {![string length $dtext]} {
  736.     if {!$storeResult} {
  737.         alertnote    "No difference:\r${Diff::1}\r${Diff::2}"
  738.     }
  739.     return 0
  740.     } else {    
  741.     # If requested, return the diff result in Diff::result, 
  742.     # rather than opening a diff window
  743.     if {$storeResult} {
  744.         set Diff::result $dtext
  745.     } else {
  746.         Diff::diffWindow $dtext $name            
  747.     }
  748.     return 1
  749.     }
  750. }
  751.  
  752. proc Diff::displayAll {{name "* File Comparison *"}} {
  753.     global Diff::1 Diff::2 Diff::result
  754.     
  755.     Diff::Display Diff::1 1 0 1
  756.     Diff::Display Diff::2 0 0 1
  757.     Diff::diffWindow ${Diff::result} $name    
  758. }
  759.  
  760. proc Diff::diffWindow {diffText {name {* File Comparison *}}} {
  761.     global tileLeft tileTop tileWidth tileHeight    
  762.     
  763.     set top [expr {$tileTop + $tileHeight - 178}]
  764.     set n [new -n $name -g $tileLeft $top [expr {$tileWidth - 6}] 178 \
  765.       -m Diff -info "\r$diffText\r"]
  766.     select [minPos] [nextLineStart [minPos]]
  767.     Diff::opening $n
  768. }
  769.  
  770. # ◊◊◊◊ Moving around ◊◊◊◊ #
  771. proc Diff::Up {} {
  772.     set res [search -s -f 0 -r 1 -- "^\[^- \n\r\]+(\r|\n|\$)" [pos::math [getPos] - 1]]
  773.     set pos [lindex $res 0]
  774.     select $pos [nextLineStart $pos]
  775.     display $pos
  776.     refresh
  777. }
  778.  
  779. proc Diff::Down {} {
  780.     set res [search -s -f 1 -r 1 -- "^\[^- \n\r\]+(\r|\n|\$)" [pos::math [getPos] + 1]]
  781.     set pos [lindex $res 0]
  782.     select $pos [nextLineStart $pos]    
  783.     display $pos
  784.     refresh
  785. }
  786.  
  787. proc Diff::Select {} {
  788.     global Diff::1 Diff::2 diffDir
  789.     
  790.     set text [getText [lineStart [getPos]] [pos::math [nextLineStart [getPos]] - 1]]
  791.     
  792.     if {![regexp {[acd]} $text char]} return
  793.     set res [split $text $char]
  794.     if {![scan [lindex $res 0] "%d" one]} return
  795.     if {![scan [lindex $res 1] "%d" two]} return
  796.     if {$one == 1} {incr one}
  797.     if {$two == 1} {incr two}
  798.     
  799.     if {$diffDir} {
  800.     set res [search -s -f 0 -r 1 "^diff.*(\r|\n|\$)" [getPos]]
  801.     set text [eval getText $res]
  802.     set len [llength $text]
  803.     set Diff::1 [lindex $text [expr {$len - 2}]]
  804.     set Diff::2 [lindex $text [expr {$len - 1}]]
  805.     }
  806.     Diff::Display Diff::1 1 [expr {$one - 1}] $diffDir
  807.     Diff::Display Diff::2 0 [expr {$two - 1}] $diffDir
  808.     
  809.     if {$diffDir} {
  810.     catch {bringToFront ${Diff::window}}
  811.     }
  812. }
  813.  
  814. proc Diff::Display {name left {row 0} {check 0}} {
  815.     upvar $name wname
  816.     if {![info exists wname]} {
  817.     if {$left} {
  818.         message "Diff window for left doesn't exist"
  819.     } else {
  820.         message "Diff window for right doesn't exist"
  821.     }
  822.     return
  823.     }
  824.     if {$check} {
  825.     set geo [Diff::Geo $left]
  826.     set res [lsearch [winNames -f] "$wname"];
  827.     if { $res < 0 } {
  828.         set res [lsearch [winNames -f] "$wname <*>"];
  829.     }
  830.     if { $res < 0} {
  831.         eval edit -g $geo [list $wname]
  832.         set wname [win::Current]
  833.     } else {
  834.         set wname [lindex [winNames -f] $res]
  835.         if {[getGeometry $wname] != $geo} {
  836.         sizeWin $wname [lindex $geo 2] [lindex $geo 3]
  837.         moveWin $wname [lindex $geo 0] [lindex $geo 1]
  838.         }
  839.         if {$res > 2} {
  840.         bringToFront $wname
  841.         }
  842.     }
  843.     }
  844.     display -w $wname [rowColToPos -w $wname $row 0]
  845. }
  846.  
  847. proc Diff::viewSophisticated {} {
  848.     global Diff::1 Diff::2 diffDir DiffmodeVars Diff::Marked
  849.     global Diff::leftDir Diff::rightDir
  850.     
  851.     set text [Diff::line [getPos] files]
  852.     
  853.     if {$diffDir} {
  854.     set Diff::1 ${Diff::leftDir}[lindex $files 0]
  855.     if {![file exists ${Diff::1}]} {
  856.         if {${Diff::leftDir} == "" && ([set res [lsearch [winNames] "[file tail ${Diff::1}]*"]] != -1)} {
  857.         set Diff::1 [lindex [winNames -f] $res]
  858.         } else {
  859.         unset Diff::1
  860.         }
  861.     } else {
  862.         if {[set res [lsearch [winNames -f] "${Diff::1}*"]] != -1} {
  863.         set Diff::1 [lindex [winNames -f] $res]
  864.         }
  865.     }
  866.     set Diff::2 ${Diff::rightDir}[lindex $files 1]
  867.     if {![file exists ${Diff::2}]} {
  868.         if {${Diff::rightDir} == "" && ([set res [lsearch [winNames] "[file tail ${Diff::2}]*"]] != -1)} {
  869.         set Diff::2 [lindex [winNames -f] $res]
  870.         } else {
  871.         unset Diff::2
  872.         }
  873.     } else {
  874.         if {[set res [lsearch [winNames -f] "${Diff::2}*"]] != -1} {
  875.         set Diff::2 [lindex [winNames -f] $res]
  876.         }
  877.     }
  878.     }
  879.     regexp {([^/]+)(.*)} $text "" mark suff
  880.     if {![info exists "Diff::Marked($suff)"]} {
  881.     Diff::Display Diff::1 1 0 1
  882.     Diff::Display Diff::2 0 0 1
  883.     Diff::setMarksUp $suff
  884.     if {[info exists Diff::1]} {
  885.         Diff::mark ${Diff::1} 1 $suff
  886.         set Diff::Marked($suff) 1
  887.     }
  888.     if {[info exists Diff::2]} {
  889.         Diff::mark ${Diff::2} 0 $suff
  890.         set Diff::Marked($suff) 1
  891.     }
  892.     Diff::diffWinFront
  893.     }
  894.     set text $mark
  895.     
  896.     if {$DiffmodeVars(useMarksDontBringToFront)} {
  897.     if {![catch {mark::getRange diff-$text ${Diff::1}} range]} {
  898.         set beg [lindex $range 0]
  899.         set end [lindex $range 2]
  900.         display -w ${Diff::1} [expr \
  901.           {[pos::compare $beg > [minPos]] \
  902.           ? [pos::math $beg - 1] : $beg}]
  903.         select -w ${Diff::1} $beg $end
  904.         if {[pos::compare $beg > [minPos]]} {
  905.         set beg [pos::math $beg - 1]
  906.         }
  907.         #display -w ${Diff::1} $beg
  908.         #refresh ${Diff::1}
  909.     }
  910.     
  911.     if {![catch {mark::getRange diff-$text ${Diff::2}} range]} {
  912.         set beg [lindex $range 0]
  913.         set end [lindex $range 2]
  914.         display -w ${Diff::2} [expr \
  915.           {[pos::compare $beg > [minPos]] \
  916.           ? [pos::math $beg - 1] : $beg}]
  917.         select -w ${Diff::2} $beg $end
  918.         if {[pos::compare $beg > [minPos]]} {
  919.         set beg [pos::math $beg - 1]
  920.         }
  921.         #display -w ${Diff::2} $beg
  922.         #refresh ${Diff::2}
  923.     }
  924.     # we need this line because of an Alpha visual bug.
  925.     # Alpha will often draw the text in the wrong window when we 
  926.     # hit 'down'.  It does correct itself, but it looks silly.
  927.     Diff::diffWinFront
  928.     } else {            
  929.     if {![catch {Diff::BringToFront ${Diff::1}}]} {
  930.         gotoMark "diff-$text"
  931.     }
  932.     if {![catch {Diff::BringToFront ${Diff::2}}]} {
  933.         gotoMark "diff-$text"        
  934.     }
  935.     Diff::diffWinFront
  936.     }
  937. }
  938. proc Diff::viewOld {} {
  939.     global Diff::1 Diff::2 diffDir 
  940.     
  941.     set text [Diff::line [getPos]]
  942.     if {![regexp {[acd]} $text char]} return
  943.     set res [split $text $char]
  944.     if {![scan [lindex $res 0] "%d,%d" one oned]} return
  945.     if {![scan [lindex $res 1] "%d,%d" two twod]} return
  946.     set on $one
  947.     set tw $two
  948.     if {$on == 1} {incr on}
  949.     if {$tw == 1} {incr tw}
  950.     if {![info exists oned]} {set oned $one}
  951.     if {![info exists twod]} {set twod $two}
  952.     
  953.     if {$diffDir} {
  954.     set res [search -s -f 0 -r 1 "^diff.*(\r|\n|\$)" [getPos]]
  955.     set text [eval getText $res]
  956.     set Diff::1 [lindex $text 1]
  957.     set Diff::2 [lindex $text 2]
  958.     }
  959.     Diff::Sel Diff::1 [expr {$on - 1}] $one $oned 1
  960.     Diff::Sel Diff::2 [expr {$tw - 1}] $two $twod 0
  961.     set wins [lremove [lrange [winNames -f] 0 2] ${Diff::1} ${Diff::2}]
  962.     set wins [lremove -glob $wins *Comparison*]
  963.     if {$wins != ""} {
  964.     bringToFront ${Diff::1}
  965.     bringToFront ${Diff::2}
  966.     }
  967.     Diff::diffWinFront
  968. }
  969.  
  970. proc Diff::View {} {
  971.     global DiffmodeVars
  972.     if {$DiffmodeVars(useSophisticatedDiffMarking)} {
  973.     Diff::viewSophisticated
  974.     } else {
  975.     Diff::viewOld
  976.     }
  977. }
  978.  
  979. ## 
  980.  # -------------------------------------------------------------------------
  981.  # 
  982.  # "Diff::Sel" --
  983.  # 
  984.  #  This handles a name either with or without trailing '<n>' and fixes
  985.  #  the given name if it isn't right.
  986.  # -------------------------------------------------------------------------
  987.  ##
  988. proc Diff::Sel {wnamev ro row rowd left} {
  989.     global diffDir
  990.     upvar $wnamev wname
  991.     if {$diffDir} {
  992.     set geo [Diff::Geo $left]
  993.     if {[set res [lsearch [winNames -f] "$wname*"]] < 0} {
  994.         eval edit -g $geo [list $wname]
  995.         set wname [win::Current]
  996.     } else {
  997.         set wname [lindex [winNames -f] $res]
  998.         if {[getGeometry $wname] != $geo} {
  999.         sizeWin $wname [lindex $geo 2] [lindex $geo 3]
  1000.         moveWin $wname [lindex $geo 0] [lindex $geo 1]
  1001.         }
  1002.     }
  1003.     }
  1004.     display -w $wname [rowColToPos -w $wname $ro 0]
  1005.     select -w $wname [rowColToPos -w $wname $row 0] \
  1006.       [rowColToPos -w $wname [expr {$rowd + 1}] 0]
  1007. }
  1008.  
  1009. # ◊◊◊◊ Utilities ◊◊◊◊ #
  1010.  
  1011. proc Diff::Win {} {
  1012.     global win::Modes
  1013.     set files [winNames -f]
  1014.     set len [llength $files]
  1015.     for {set i 0} {$i < $len} {incr i} {
  1016.     if {[set win::Modes([lindex [winNames -f] $i])] == "Diff"} {
  1017.         bringToFront [lindex [winNames] $i]
  1018.         return
  1019.     }
  1020.     }
  1021.     beep
  1022.     message "No Diff window."
  1023. }
  1024.  
  1025. proc Diff::Geo {left} {
  1026.     global tileWidth tileHeight tileTop tileLeft
  1027.     
  1028.     set margin 4
  1029.     set width [expr {($tileWidth - $margin)/2}]
  1030.     set height [expr {$tileHeight - 200}]
  1031.     set hor $tileLeft
  1032.     
  1033.     if {!$left} {incr hor [expr {$width+$margin}]}
  1034.     
  1035.     return [list $hor $tileTop $width $height]
  1036. }
  1037.  
  1038. proc Diff::diffWinFront {} {
  1039.     global Diff::window
  1040.     catch {bringToFront ${Diff::window}}
  1041. }
  1042.  
  1043. ## 
  1044.  # -------------------------------------------------------------------------
  1045.  # 
  1046.  # "Diff::BringToFront" --
  1047.  # 
  1048.  #  Hack to make it quicker to switch between windows.  We often want
  1049.  #  the 'Diff' window to be in the front all the time, but have to
  1050.  #  bring others to the front temporarily for manipulation.  This proc
  1051.  #  brings a different window to the front more quickly by avoiding
  1052.  #  all mode-changing code.  Of course you should only call this proc
  1053.  #  when you will _very_ soon bring a different window to the front.
  1054.  # -------------------------------------------------------------------------
  1055.  ##
  1056. proc Diff::BringToFront {w} {
  1057.     global win::Modes DiffmodeVars
  1058.     if {$DiffmodeVars(useFastWindowSwapping)} {
  1059.     set oldm [set win::Modes($w)]
  1060.     set win::Modes($w) Diff
  1061.     if {[catch {bringToFront $w}]} {
  1062.         unset win::Modes($w)
  1063.         beep
  1064.         error "no such win"
  1065.     } else {
  1066.         set win::Modes($w) $oldm
  1067.     }
  1068.     } else {
  1069.     bringToFront $w
  1070.     }
  1071. }
  1072.